﻿//This C# code file was released under the Ms-PL license
//http://www.opensource.org/licenses/ms-pl.html
//This script was originally intended for use with Microsoft Dynamics AX
//and maintained and distributed as a project on CodePlex
//http://dynamicsaxbuild.codeplex.com

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;
using System.Reflection;
using System.Activities;
using Microsoft.Dynamics.AX.Framework.Tools.ModelManagement;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using System.Diagnostics;


namespace AXTFSBuildLibrary
{
    public enum AxModelAction
    {
        INSTALL,
        UNINSTALL,
        DELETELAYER,
        //REPLACE,      //NOT IMPLEMENTED
        EXPORT,
        CREATE,
        SETNOINSTALLMODE
    }

    [BuildActivity(HostEnvironmentOption.All)]
    public sealed class AxModel : CodeActivity
    {
        /// <summary>
        /// Name of the AOS server (machine)
        /// </summary>
        [RequiredArgument]
        public InArgument<string> ServerName { get; set; }
        /// <summary>
        /// The AOS port number
        /// </summary>
        [RequiredArgument]
        public InArgument<UInt16> PortNumber { get; set; }
        /// <summary>
        /// The action to take on the model store
        /// </summary>
        [RequiredArgument]
        public InArgument<AxModelAction> Action { get; set; }

        public InArgument<string> Filename { get; set; }
        public InArgument<string> ModelManifestFilename { get; set; }
        public InArgument<string> ModelName { get; set; }
        public InArgument<string> ModelDisplayName { get; set; }
        public InArgument<string> ModelPublisher { get; set; }
        public InArgument<string> Layer { get; set; }
        public InArgument<string> ModelDescription { get; set; }
        public InArgument<string> ModelVersion { get; set; }
        public InArgument<string> ModelSigningKey { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            AxModelAction action = Action.Get(context);
            string aosName = ServerAOSName(ServerName.Get(context), PortNumber.Get(context).ToString());

            switch (action)
            {
                case AxModelAction.INSTALL:
                    InstallModel(context, aosName, Filename.Get(context), ModelManifestFilename.Get(context));
                    break;
                case AxModelAction.UNINSTALL:
                    if (!String.IsNullOrEmpty(ModelManifestFilename.Get(context)))
                    {
                        UninstallModel(context, aosName, ModelManifestFilename.Get(context));
                    }
                    else
                    {
                        UninstallModel(context, aosName, ModelName.Get(context), ModelPublisher.Get(context));
                    }
                    break;
                case AxModelAction.DELETELAYER:
                    DeleteLayer(context, aosName, Layer.Get(context));
                    break;
                // NOT IMPLEMENTED
                //case AxModelAction.REPLACE:
                //    break;
                case AxModelAction.EXPORT:
                    if (!String.IsNullOrEmpty(ModelManifestFilename.Get(context)))
                    {
                        ExportModel(context, aosName, ModelManifestFilename.Get(context), Filename.Get(context), ModelSigningKey.Get(context));
                    }
                    else
                    {
                        ExportModel(context, aosName, ModelName.Get(context), ModelPublisher.Get(context), Filename.Get(context), ModelSigningKey.Get(context));
                    }
                    break;
                case AxModelAction.CREATE:
                    if (!String.IsNullOrEmpty(ModelManifestFilename.Get(context)))
                    {
                        CreateModel(context, aosName, ModelManifestFilename.Get(context), ModelVersion.Get(context), ModelDescription.Get(context));
                    }
                    else
                    {
                        CreateModel(context, aosName, ModelName.Get(context), ModelPublisher.Get(context), Layer.Get(context), ModelDisplayName.Get(context), ModelDescription.Get(context), ModelVersion.Get(context));
                    }
                    break;
                case AxModelAction.SETNOINSTALLMODE:
                    SetNoInstallMode(context, aosName);
                    break;
            }
        }

        public static void SetNoInstallMode(CodeActivityContext context, string aosName)
        {
            AxUtilContext utilContext = new AxUtilContext();
            AxUtilConfiguration config = new AxUtilConfiguration();
            config.AOSConfiguration = aosName;

            AxUtil util = new AxUtil();
            util.Config = config;
            util.Context = utilContext;
            util.ApplyInstallModeState(InstallModeState.NoInstallMode);
        }

        public static void UninstallModel(CodeActivityContext context, string aosName, string manifestFile)
        {
            ModelManifest manifest = ModelManifest.Read(manifestFile);

            UninstallModel(context, aosName, manifest.Name, manifest.Publisher);
        }

        public static bool ModelExists(CodeActivityContext context, string aosName, string modelName, string modelPublisher)
        {
            AxUtilContext utilContext = new AxUtilContext();
            AxUtilConfiguration config = new AxUtilConfiguration();
            config.AOSConfiguration = aosName;
            bool modelFound = false;

            AxUtil util = new AxUtil();

            config.ModelArgument = new ModelArgument(modelName, modelPublisher);

            IList<ModelManifest> list = util.List(utilContext, config);
            foreach (ModelManifest manifest in list)
            {
                if (manifest.Name == modelName && manifest.Publisher == modelPublisher)
                    modelFound = true;
            }

            return modelFound;
        }

        public static void UninstallModel(CodeActivityContext context, string aosName, string modelName, string modelPublisher)
        {
            AxUtilContext utilContext = new AxUtilContext();
            AxUtilConfiguration config = new AxUtilConfiguration();
            config.AOSConfiguration = aosName;

            AxUtil util = new AxUtil();

            config.ModelArgument = new ModelArgument(modelName, modelPublisher);

            if (ModelExists(context, aosName, modelName, modelPublisher))
            {
                util.Delete(utilContext, config);
            }

            if (utilContext.ExecutionStatus == ExecutionStatus.Error)
            {
                foreach (string error in utilContext.Errors)
                {
                    context.TrackBuildError(error);
                }
                throw new Exception("Model uninstall failed.");
            }
        }

        public static void DeleteLayer(CodeActivityContext context, string aosName, string layer)
        {
            AxUtilContext utilContext = new AxUtilContext();
            AxUtilConfiguration config = new AxUtilConfiguration();
            config.AOSConfiguration = aosName;

            AxUtil util = new AxUtil();

            config.Layer = layer;

            util.Delete(utilContext, config);

            if (utilContext.ExecutionStatus == ExecutionStatus.Error)
            {
                foreach (string error in utilContext.Errors)
                {
                    context.TrackBuildError(error);
                }
                throw new Exception("Layer deletion failed.");
            }
        }

        public static void InstallModel(CodeActivityContext context, string aosName, string modelFile, string modelManifest)
        {
            AxUtilContext utilContext = new AxUtilContext();
            AxUtilConfiguration config = new AxUtilConfiguration();
            config.AOSConfiguration = aosName;

            AxUtil util = new AxUtil();

            config.ImportFiles.Add(modelFile);
            IList<ModelContents> importedModels = util.Import(utilContext, config);
            if (utilContext.ExecutionStatus == ExecutionStatus.Error)
            {
                foreach (string error in utilContext.Errors)
                {
                    context.TrackBuildError(error);
                }
                throw new Exception("Model install failed.");
            }

            if (importedModels == null || importedModels.Count != 1)
            {
                throw new Exception("Model install failed.");
            }
        }

        public static void ExportModel(CodeActivityContext context, string aosName, string manifestFile, string modelFile, string key)
        {
            ModelManifest manifest = ModelManifest.Read(manifestFile);

            ExportModel(context, aosName, manifest.Name, manifest.Publisher, modelFile, key);
        }

        public static void ExportModel(CodeActivityContext context, string aosName, string modelName, string modelPublisher, string modelFile, string key)
        {
            ExportModelPS(context, aosName, modelName, modelPublisher, modelFile, key);

            /*
             * There is a reported bug where exported model is .NET 4.0 instead of 2.0
             *  which then results in a "invalid format" error when trying to import the model
             *  from the command line tools
             * 
             * Only current workaround is to use the command line tools.
             * 
             * Below code works, but model is .NET 4.0
             
            
            AxUtilContext utilContext = new AxUtilContext();
            AxUtilConfiguration config = new AxUtilConfiguration();
            config.AOSConfiguration = aosName;

            config.ExportFile = modelFile;
            config.ModelArgument = new ModelArgument(modelName, modelPublisher);

            if (!String.IsNullOrEmpty(key))
            {
                config.StrongNameKeyFile = key;
            }
            AxUtil util = new AxUtil();

            util.Export(utilContext, config);

            if (utilContext.ExecutionStatus == ExecutionStatus.Error)
            {
                foreach (string error in utilContext.Errors)
                {
                    context.TrackBuildError(error);
                }
                throw new Exception("Model export failed.");
            }

            if (!System.IO.File.Exists(modelFile))
            {
                throw new Exception("Model export failed.");
            }
            */
        }

        public static void ExportModelPS(CodeActivityContext context, string aosName, string modelName, string modelPublisher, string modelFile, string key)
        {
            RegistryKey AXInstall = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Dynamics\6.0\Setup");
            string path = AXInstall.GetValue("InstallDir") + @"\ManagementUtilities\";

            string parameters = String.Format("export /config:{0} \"/model:{1}\" \"/file:{3}\"", aosName, modelName, modelPublisher, modelFile);

            if (!String.IsNullOrEmpty(key))
            {
                parameters = String.Format("{0} \"/key:{1}\"", parameters, key);
            }

            ProcessStartInfo processStartInfo = new ProcessStartInfo(path + "axutil.exe", parameters);
            processStartInfo.WindowStyle = ProcessWindowStyle.Minimized;
            processStartInfo.WorkingDirectory = path;

            Process process = Process.Start(processStartInfo);

            try
            {
                process.WaitForExit();
            }
            catch
            {
                throw new Exception("Error exporting model.");
            }
        }

        public static void CreateModel(CodeActivityContext context, string aosName, string modelName, string modelPublisher, string layer, string displayName, string description, string version)
        {
            AxUtilContext utilContext = new AxUtilContext();
            AxUtilConfiguration config = new AxUtilConfiguration();
            config.AOSConfiguration = aosName;

            AxUtil util = new AxUtil();

            //config.ExportFile = modelFile;
            config.ModelArgument = new ModelArgument(modelName, modelPublisher);
            config.Layer = layer;

            ModelManifest manifest = new ModelManifest();
            manifest.Name = modelName;
            manifest.Publisher = modelPublisher;
            manifest.Version = version;
            manifest.DisplayName = displayName;
            manifest.Description = description;
            bool created = util.Create(utilContext, config, manifest);

            if (utilContext.ExecutionStatus == ExecutionStatus.Error)
            {
                foreach (string error in utilContext.Errors)
                {
                    context.TrackBuildError(error);
                }
                throw new Exception("Model creation failed.");
            }

            if (!created)
            {
                throw new Exception("Model creation failed.");
            }
        }

        public static void CreateModel(CodeActivityContext context, string aosName, string manifestFile, string version, string description)
        {
            AxUtilContext utilContext = new AxUtilContext();
            AxUtilConfiguration config = new AxUtilConfiguration();
            config.AOSConfiguration = aosName;

            AxUtil util = new AxUtil();

            ModelManifest manifest = ModelManifest.Read(manifestFile);
            if (!String.IsNullOrEmpty(version))
            {
                manifest.Version = version;
            }
            if (!String.IsNullOrEmpty(description))
            {
                manifest.Description = description;
            }

            config.ModelArgument = new ModelArgument(manifest.Name, manifest.Publisher);
            config.Layer = manifest.Layer.ToString();

            bool created = util.Create(utilContext, config, manifest);

            if (utilContext.ExecutionStatus == ExecutionStatus.Error)
            {
                foreach (string error in utilContext.Errors)
                {
                    context.TrackBuildError(error);
                }
                throw new Exception("Model creation failed.");
            }

            if (!created)
            {
                throw new Exception("Model creation failed.");
            }
        }

        protected static string ServerAOSName(string serverName, string portNumber)
        {
            string aosRegistryPath = @"SYSTEM\CurrentControlSet\services\Dynamics Server\6.0";
            string aosName = "";
            RegistryKey aosEntries = null;

            if (serverName != System.Environment.MachineName)
            {
                // Open the registry on the remote machine
                aosEntries = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, serverName);
                // Get the list of servers running on the remote machine.
                aosEntries = aosEntries.OpenSubKey(aosRegistryPath);
            }
            else
            {
                // Get the list of servers running on this machine.
                aosEntries = Registry.LocalMachine.OpenSubKey(aosRegistryPath);
            }

            string[] aosRegistryEntries = aosEntries.GetSubKeyNames();
            foreach (string aosRegistryEntry in aosRegistryEntries)
            {
                RegistryKey aosRootKey = Registry.LocalMachine.OpenSubKey(aosRegistryPath + @"\" + aosRegistryEntry);
                RegistryKey aosInstanceKey = Registry.LocalMachine.OpenSubKey(aosRegistryPath + @"\" + aosRegistryEntry + @"\" + aosRootKey.GetValue("Current"));
                if (aosInstanceKey.GetValue("Port").Equals(portNumber))
                {
                    aosName = aosRootKey.GetValue("InstanceName").ToString();
                    break;
                }
            }

            if (String.IsNullOrEmpty(aosName))
            {
                throw new Exception("Could not find configuration for server running on port " + portNumber);
            }

            return aosName;
        }

        static AxModel()
        {
            AppDomain currentDomain = AppDomain.CurrentDomain;
            currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
        }

        static private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
        {
            //This handler is called only when the common language runtime tries to bind to the assembly and fails.

            //Retrieve the list of referenced assemblies in an array of AssemblyName.
            Assembly MyAssembly = null, objExecutingAssemblies;
            string strTempAssmbPath = "";

            objExecutingAssemblies = Assembly.GetExecutingAssembly();
            AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();

            //Loop through the array of referenced assembly names.
            foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
            {
                string name = args.Name.Substring(0, args.Name.IndexOf(","));
                //Check for the assembly names that have raised the "AssemblyResolve" event.
                if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == name)
                {
                    if (name == "AXUtilLib")
                    {
                        //Build the path of the assembly from where it has to be loaded.				
                        RegistryKey AXInstall = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Dynamics\6.0\Setup");
                        strTempAssmbPath = AXInstall.GetValue("InstallDir") + @"\ManagementUtilities\" + name + ".dll";
                    }
                    break;
                }

            }

            if (!String.IsNullOrEmpty(strTempAssmbPath))
            {
                //Load the assembly from the specified path. 					
                MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
            }

            //Return the loaded assembly.
            return MyAssembly;
        }
    }
}
